import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Vector;

import javax.bluetooth.BluetoothStateException;
import javax.bluetooth.DeviceClass;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.DiscoveryListener;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.UUID;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;

/**
 * 
 * @author hanyzh
 */
public class BlueToothManager extends Thread implements DiscoveryListener, IBlueToothManager {

	private Thread thread = null;
	private static final String BLUE_TOOTH_PROTOCOL = "btspp://";
	private static final String HOST_ADDRESS = "localhost";
	private static final String SERVER_NAME = "Mobile_Pong_Server";
	private static final String SERVER_UUID = "FFEEDDCCBBAA99887766554433221100";
	private static final int MAX_MSG_LENGTH = message.MAX_PACKET_SIZE + 3;// 23;
	private static final int MAX_PACKET_SIZE = message.MAX_PACKET_SIZE;
	private byte[] prefix = { 120, 119, 118 };
	private int prefixLen = prefix.length;
	private byte[] x_msg = new byte[prefixLen + MAX_PACKET_SIZE];
	private byte[] origMsg = new byte[MAX_PACKET_SIZE];
	private String myName = null;
	/**
	 * Server's UUID
	 */
	private static UUID MOBILE_PONG_SERVER_UUID = new UUID(SERVER_UUID, false);
	private LocalDevice localDevice = null;
	private StreamConnectionNotifier notifier = null;
	/**
	 * The network connection
	 */
	private StreamConnection streamConnection = null;
	/**
	 * Input stream
	 */
	private InputStream inputStream = null;
	/**
	 * Output stream
	 */
	private OutputStream outputStream = null;
	private boolean isBlueToothReady = false;
	private boolean isGameOver = false;
	private boolean isServer = true;
	private boolean isRecvThreadReady = false;
	private boolean isRemoteReady = false;
	/**
	 * Message Receiver Thread
	 */
	private MsgReceiver msgReceiver = null;
	private IMainGame parent = null;

	// Following fields are defined for client players
	/**
	 * The result of the client search for available devices
	 */
	private ServiceRecord remoteServiceRecord = null;
	private RemoteDevice remoteDevice = null;
	private Vector discoveredDevices = null;

	/**
	 * Constructor
	 * 
	 * @param parent
	 *            the object that create this instance, must implement IMainGame
	 *            interface
	 * @param isServer
	 *            true: current party will establish and publish a BlueTooth
	 *            Service so that other clients can connect with it false:
	 *            current party is a client, it will try to search available
	 *            service from a server
	 */
	public BlueToothManager(IMainGame parent, boolean isServer) {

		System.out.println("BlueToothManager constructor!==========================================================");
		// We need to save the parent object before we do other things
		this.parent = parent;
		this.isServer = isServer;
		this.discoveredDevices = new Vector();
		if (isServer == true) {
			myName = "SERVER ";
		} else {
			myName = "CLIENT ";
		}

		for (int i = 0; i < prefixLen; i++) {
			x_msg[i] = prefix[i];
		}

		// Run server by starting a new thread
		this.thread = new Thread(this);
		this.thread.start();
	}

	public void run() {
		System.out.println("BlueToothManager::run()==========================================================");
		isBlueToothReady = false;
		try {
			if (this.isServer == true) {
				serverExec();
			} else {
				clientExec();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		try {
			parent.updateIndicator(null, true);
			if (isBlueToothReady) {
				System.out.println("BlueToothManager::run() @@ this.isBlueToothReady=true ==========================================================");
				communicate();
				while (this.isRecvThreadReady == false || this.isRemoteReady == false) {
					if (this.isRemoteReady == false) {
						outputStream.write("BT:??I_AM_READY??".getBytes());
						outputStream.flush();
						sleep(50);
					}
				}
				parent.updateIndicator(null, true);
			}
		} catch (Exception exception) {	
			// e.printStackTrace();
			isBlueToothReady = false;
		}

		// We need to notify the parent whether we are ready or not
		//System.err.println(myName + System.currentTimeMillis() + " INVOKES parent.btInitComplete()");

		parent.btInitComplete(isBlueToothReady);
	}

	void serverExec() throws BluetoothStateException, IOException, InterruptedException {
		System.out.println("BlueToothServer::run1()==========================================================");
		// Get a local Device
		localDevice = LocalDevice.getLocalDevice();

		if (localDevice == null) {
			// throw new NullPointerException("Fail to get a local Device!");
			return;
		}
		
		parent.updateIndicator(null, true);
		
		System.out.println("BlueToothServer::run2()==========================================================");
		localDevice.setDiscoverable(DiscoveryAgent.GIAC);

		// generate a connection URL to create a notifier
		StringBuffer connUrl = new StringBuffer(BLUE_TOOTH_PROTOCOL);
		connUrl.append(HOST_ADDRESS).append(":");
		connUrl.append(MOBILE_PONG_SERVER_UUID.toString()).append(";");
		connUrl.append("name=").append(SERVER_NAME);
		connUrl.append(";authorize=false");

		System.out.println("BlueToothServer::run3()==========================================================");
		notifier = (StreamConnectionNotifier) Connector.open(connUrl.toString());
		parent.updateIndicator("Waiting for client...", true);
		// accept one client connection
		try {
			this.streamConnection = notifier.acceptAndOpen();
			parent.updateIndicator("Connection accepted and opened!", false);
			this.inputStream = this.streamConnection.openInputStream();
			this.outputStream = this.streamConnection.openOutputStream();
		} catch (Exception e) {
			;
		}
		parent.updateIndicator(null, true);
		System.out.println("BlueToothServer::run4()==========================================================");
		// Hide our server so that only one client can connect to the server
		try {
			// notifier.close();
			// localDevice.setDiscoverable(DiscoveryAgent.NOT_DISCOVERABLE);
			// localDevice.setDiscoverable(DiscoveryAgent);
		} catch (Exception e) {
		}
		this.isBlueToothReady = true;
	}

	void clientExec() throws BluetoothStateException, InterruptedException,
			IOException {
		System.out.println("--Client::run1()--------------");
		localDevice = LocalDevice.getLocalDevice();
		DiscoveryAgent discoveryAgent = localDevice.getDiscoveryAgent();
		discoveryAgent.startInquiry(DiscoveryAgent.GIAC, this);

		parent.updateIndicator("Searching devices...", false);
		synchronized (this) {
			wait();
		}
		parent.updateIndicator(null, true);
		
		UUID[] uuids = new UUID[2];
		// this indicates socket communications. refer to some demo codes
		uuids[0] = new UUID(0x1101);
		// this is the server service
		uuids[1] = new UUID(SERVER_UUID, false);
		if (this.discoveredDevices == null || this.discoveredDevices.size() == 0) {
			return;
		}
		parent.updateIndicator("Find " + this.discoveredDevices.size() + " devices!", false);
		for (int i = 0, len = this.discoveredDevices.size(); i < len; i++) {
			try {
				remoteDevice = (RemoteDevice) discoveredDevices.elementAt(i);
				discoveryAgent.searchServices(null, uuids, remoteDevice, this);
			} catch (Exception e) {
				// e.printStackTrace();
			}
		}
		parent.updateIndicator("Searching services...", false);
		synchronized (this) {
			wait();
		}
		parent.updateIndicator(null, true);
		System.out.println("--Client::run2()--------------");

		if (remoteServiceRecord != null) {
			System.out.println("--Client::run3()--------------");
			parent.updateIndicator("Game service found! Connecting to server...", false);
			// open the connection
			String connUrl = remoteServiceRecord.getConnectionURL(ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
			streamConnection = (StreamConnection) Connector.open(connUrl);
			this.outputStream = streamConnection.openOutputStream();
			this.inputStream = streamConnection.openInputStream();
			parent.updateIndicator("Connection established!", true);
			this.isBlueToothReady = true;
		} else {
			// System.err.println("failed to find remote player");
			isBlueToothReady = false;
		}
	}

	/**
	 * Send and receive from the opponent(Client)
	 */
	private void communicate() throws IOException {
		System.err.println("BlueToothManager::communicate()==========================================================");
		msgReceiver = new MsgReceiver();
	}

	public void closeStream() {
		isGameOver = true;
		try {
			this.msgReceiver = null;
		} catch (Exception e) {
			;
		}
		if (this.inputStream != null) {
			try {
				this.inputStream.close();
				this.inputStream = null;
			} catch (IOException e) {
				;
			}
		}
		if (this.outputStream != null) {
			try {
				this.outputStream.close();
				this.outputStream = null;
			} catch (IOException e) {
				;
			}
		}
		if (this.streamConnection != null) {
			try {
				this.streamConnection.close();
				this.streamConnection = null;
			} catch (Exception e) {
				;
			}
		}
	}

	/**
	 * Implementation of DiscoveryListener.
	 */
	public void deviceDiscovered(RemoteDevice remoteDevice, DeviceClass deviceClass) {
		System.out.println("BlueToothManager::deviceDiscovered()==========================================================");
		// this.remoteDevice = remoteDevice;
		if (discoveredDevices.indexOf(remoteDevice) == -1) {
			discoveredDevices.addElement(remoteDevice);
			parent.updateIndicator("Device discovered:" + remoteDevice.getBluetoothAddress(), false);
		}
	}

	/**
	 * Implementation of DiscoveryListener.
	 */
	public void inquiryCompleted(int discoveryType) {
		System.out.println("BlueToothManager::inquiryCompleted()==========================================================");
		try {
			synchronized (this) {
				notify();
			}
		} catch (Exception e) {
			;
		}
	}

	/**
	 * Complete finding remote device's service
	 */
	public void serviceSearchCompleted(int id, int respCode) {
		System.out.println("BlueToothManager::serviceSearchCompleted()==========================================================");
		try {
			synchronized (this) {
				notify();
			}
		} catch (Exception e) {
			;
		}
	}

	/**
	 * Find a remote device's service
	 */
	public void servicesDiscovered(int arg0, ServiceRecord[] arg1) {
		System.out.println("BlueToothManager::servicesDiscovered()==========================================================");

		if ((arg1 == null)) {
		} else {
			parent.updateIndicator("" + arg1.length + "services discovered!", false);
		}
		if (remoteServiceRecord == null) {
			for (int i = 0, len = arg1.length; i < len; i++) {
				if (arg1[i] != null) {
					remoteServiceRecord = arg1[i];
					break;
				}
			}
			/* synchronized (this) { notify(); } */
		}
	}

	public synchronized void sendMsg(byte[] message) {
		if (this.isBlueToothReady) {
			try {
				System.arraycopy(message, 0, x_msg, prefixLen, MAX_PACKET_SIZE);
				outputStream.write(x_msg);
				outputStream.flush();
				//System.err.println(myName + " " + System.currentTimeMillis() + " send:" + new String(message));
			} catch (Exception ex) {
				;
			}
		}
	}

	public class MsgReceiver extends Thread {

		private Thread thread;

		MsgReceiver() {
			thread = new Thread(this);
			thread.start();
		}

		public void run() {
			byte[] receivedMsg = new byte[MAX_MSG_LENGTH];
			int len = -1;
			int idx = -1;
			isRecvThreadReady = true;
			
			// send/receive thread synchornization
			while (isGameOver == false) {
				try {
					len = inputStream.read(receivedMsg);
					idx = -1;
					if (len > 1) {
						String msg = new String(receivedMsg);
						if (msg.indexOf("??I_AM_READY??") != -1) {
							if (msg.indexOf("??I_AM_READY??") != -1) {
								isRemoteReady = true;
							}
						} else {
							break;
						}
						receivedMsg = new byte[MAX_MSG_LENGTH];
					}
				} catch (IOException ex) {
					// ex.printStackTrace();
					// receive interrupted...
				} catch (Exception e) {
					// e.printStackTrace();
					// receive interrupted...
				}
			}

			// receive msg that is sent from game app
			try {
				while (!isGameOver) {
					if (len > 1) {
						idx = 2;
						while (idx < len && receivedMsg[idx - 2] != 119 && receivedMsg[idx - 1] != 118) {
							idx++;
						}
						// while (idx < len) {
						if (idx < len) {
							System.arraycopy(receivedMsg, idx, origMsg, 0, MAX_PACKET_SIZE);
							try {
								//System.err.println(myName + " " + System.currentTimeMillis() + " receive:" + new String(origMsg));
								parent.msgProcessor(origMsg, origMsg.length);
							} catch (Exception e) {
								//e.printStackTrace();
								System.err.println("ERROR OCCURRED IN MESSAGE PROCESSOR!");
							}
						}
						sleep(20);
						receivedMsg = new byte[MAX_MSG_LENGTH];
					}
					len = inputStream.read(receivedMsg);
				}
			} catch (Exception e) {
				// e.printStackTrace();
				isGameOver = true;
				// receive interrupted... maybe the remote player has been down
			}
		}
	}

	public boolean isIsGameOver() {
		return isGameOver;
	}

	public void setIsGameOver(boolean isGameOver) {
		this.isGameOver = isGameOver;
	}
}